package Network;
import Network.NetworkAddress;
import baseservergrid.ModeCompilation;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.NullPointerException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URLConnection;
import java.rmi.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import javax.swing.JOptionPane;
import mpiserver.MpiEvent;
import mpiserver.MpiListener;

/**
 * Реализация интерфейса IArbitrationController
 * @author Volk ID = 1001
 * @see IArbitrationController
 *
 */
public class ArbitrationController implements IArbitrationController, IModeWork//, ModeCompilation
{
  //---------------------------Константы класса---------------------------------
  /**
   * Имя удалённой заглушки предоставляющие сервисы подчинённого сервера
  */
  private final String STUBSLAVENAME = "SlaveServerController";
  /** Путь до файла настроек */
  private final String SETTINGS_PATH="./server_address.ini";
  /** Формат файла настроек */
  private final String FORMATV = "version 1.1";
  //---------------------------Аттрибуты класса---------------------------------  
  //Хэш таблица выступающая в виде таблицы в котором храниться пара <хост, заглушка подчинённого сервера>
  private HashMap<NetworkAddress, ISlaveServer> slaveServerList = new HashMap<NetworkAddress, ISlaveServer>();
  private LinkedList<PingService> listPingSS = new LinkedList<PingService>(); //Список сервисов для пинга ведомых серверов
  //Список проблемных,серверов которые необходимо всеми силами добавить
  private HashMap<NetworkAddress, ReconnectService> listReconnectSS = new HashMap<NetworkAddress, ReconnectService>();
  private long generatorServerID = 1; //Генератор случайных идентификаторов
  //Список ведомых серверов которые обновили свой параметры----------------
  private HashMap<NetworkAddress, NetworkAddress> slaveUpdateConfigList = new HashMap<NetworkAddress, NetworkAddress>();
  //-----------------------------------------------------------------------

  //Списки обработчики событий
   //Список обработчиков события: сервер стал арбитром
   private LinkedList<ActionListener> listActionModeArbitrator = new LinkedList<ActionListener>();
   //Список обработчиков события: добавлен новый ведомый сервер
   private LinkedList<ActionListener> listActionAddSlaveArbitrator = new LinkedList<ActionListener>();
   //Список обработчиков события: Удалён ведомый сервер
   private LinkedList<ActionListener> listRemoveServerListener = new LinkedList<ActionListener>();
   //Список обработчиков падения сервера
   private LinkedList<ActionListener> listFailListener = new LinkedList<ActionListener>();
   //Список обработчиков восстановления работы сервера
   private LinkedList<ActionListener> listReconnectListener = new LinkedList<ActionListener>();
   //Список обработчиков восстановления работы ведомого сервера после падения
   private LinkedList<ActionListener> listReconnectAfterFailListener = new LinkedList<ActionListener>();
  //----------------------------------------------------------------------------

  //---------------------------Сервисные методы---------------------------------

  //Соединиться со всеми серверами
   public void connectAll(){
   HashMap<NetworkAddress, ISlaveServer> temp = new HashMap<NetworkAddress, ISlaveServer>(slaveServerList);
   slaveServerList.clear();
   Iterator<NetworkAddress> iter = temp.keySet().iterator();
   while(iter.hasNext()){
    addNewSlaveServerWithServices(iter.next());
   }
  }

  /**
   * Преобразовать исходную коллекцию в список списков, каждый список списков содержит адреса
   * с одинаковыми айпишниками
   * @param collection - исходная коллекция
   */
  private LinkedList<LinkedList<NetworkAddress>> toLinkedList2(Collection<NetworkAddress> collection){
   Iterator<NetworkAddress> iter = collection.iterator();
   LinkedList<LinkedList<NetworkAddress>> res = new LinkedList<LinkedList<NetworkAddress>>();
   NetworkAddress temp;
   while(iter.hasNext()){
    temp = iter.next();
    //Проход по спискам
    LinkedList<NetworkAddress> lstTmp;
    Iterator<LinkedList<NetworkAddress>> lstIter = res.iterator();
    //Организуем поиск списка содержащего текущий IP-адрес
    boolean searchRes = false; //Нашли ли мы список с заданным IP-адрес
    while(lstIter.hasNext()){
     lstTmp = lstIter.next();
      if(lstTmp.get(0).getHost().equals(temp.getHost())){
       lstTmp.add(temp);
       searchRes = true;
       break;
      }
    }
    //Добавить новый IP-адрес в список
    if(!searchRes){
      lstTmp = new LinkedList<NetworkAddress>();
      lstTmp.add(temp);
      res.add(lstTmp);
    }
   }

   return res;
  }

  /**
   * Метод преобразующий исходные списки адресов, в списки содержащие отрицания пересечений двух этих множеств
   * @param op1 первый операнд
   * @param op2 второй операнд
   */
  private void notCrossPost(LinkedList<NetworkAddress> op1, LinkedList<NetworkAddress> op2){
    Iterator<NetworkAddress> iter1 = op1.iterator();
    //Ищем отрицание пересечения исходных подмножеств
    while(iter1.hasNext()){
      NetworkAddress address1 = iter1.next();
      Iterator<NetworkAddress> iter2 = op2.iterator();

      while(iter2.hasNext()){
        NetworkAddress address2 = iter2.next();
        //Если порты совпали то удаляем их в силу условия отрицания
        if(address1.getPort() == address2.getPort()){
         iter1.remove();
         iter2.remove();
         break;
        }
      }
    }
  }

  /**
   * Метод преобразующий исходные коллекции, до состояния двух подмножеств отрицания пересечения исходных
   * операндов
   * @param op1 - первое множество
   * @param op2 - второе множество
   */
  private void notCrossSetLinkedList(LinkedList<LinkedList<NetworkAddress>> op1, LinkedList<LinkedList<NetworkAddress>> op2){
    Iterator<LinkedList<NetworkAddress>> iter1 = op1.iterator();
    //Ищем отрицание пересечения исходных подмножеств
    while(iter1.hasNext()){
     LinkedList<NetworkAddress> lst1 = iter1.next();
     Iterator<LinkedList<NetworkAddress>> iter2 = op2.iterator();
     //Поиск нужного списка
     while(iter2.hasNext()){
      LinkedList<NetworkAddress> lst2 = iter2.next();
      if(lst1.get(0).getHost().equals(lst2.get(0).getHost())){
       //Если IP-адреса совпали, то ищем отрицания пересения портов
       notCrossPost(lst1,lst2);
       //Изли изменений не было, то удаляем оба подмножества
       if(lst1.isEmpty()) iter1.remove();
       if(lst2.isEmpty()) iter2.remove();
       break;
      }
     }

    }
  }


  /**
   * Получить адрес сервера по заглушке
   * @param server - заглушка сервера
   * @return адрес сервера или null-если сервер не найден
  */
  private NetworkAddress findServer(ISlaveServer server){
   //Получить множество пар <адрес,заглушка>
   Set<Entry<NetworkAddress,ISlaveServer>> set = slaveServerList.entrySet();
   Iterator<Entry<NetworkAddress,ISlaveServer>> iter = set.iterator();
   while(iter.hasNext()){
     Entry<NetworkAddress,ISlaveServer> entryServer = iter.next();
     //Если заглушки равны, то вернуть адрес
     if(entryServer.getValue() == server){
      return entryServer.getKey();
     }
   }
   return null;
  }


    /**
   * Инициализировать обработчики потери связи с одним из серверов или возобновления связи
   * @param ps - серсис пингования сервера, обработчик к которому необходимо прикрепить
   */
  private void initPingListener(PingService ps){
    //Добавим обработчик падения сервера--------------------------------------
    ActionListener failListener = new ActionListener(){
     public void actionPerformed(ActionEvent e) {
       NetworkAddress address = (NetworkAddress)e.getSource();
       address.SetDisable();
       //Удаление данного сервиса из списка серсисов
       Iterator<PingService> iter = listPingSS.iterator();
       while(iter.hasNext()){
        PingService temp = iter.next();
        if(temp.getAddress().equals(address)){
          temp.stopPing();
          listPingSS.remove(temp);
          break;
         }
       }
       //Если ведомый сервер изменил свою конфигурацию и его падение обусловлено перезагрузкой, то
       //в списке slaveUpdateConfigList содержится его обновлённая конфигурация
       NetworkAddress updateConfigAddress = slaveUpdateConfigList.get(address);
       if(updateConfigAddress != null){
         //Заменяем старый адрес на новый------------------------
         if(slaveServerList.containsKey(address)){
         ISlaveServer server = slaveServerList.get(address);
         slaveServerList.remove(address);
         slaveUpdateConfigList.remove(address);
         try{saveAddresses();}catch(IOException ioe){}
         }
         //-------------------------------------------------------
       address = updateConfigAddress;
       }
       //Инициализация сервиса пренудительного подключения
       ReconnectService rs = new ReconnectService(ArbitrationController.this, address);
       initReconnectService(rs);
       rs.startPing();
       activateClientListener(e, listFailListener);
       listReconnectSS.put(address, rs);
     }
    };
    ps.addFailListener(failListener);
    //-------------------------------------------------------------------------

  }

  private void initReconnectService(ReconnectService rs){
    //Добавим обработчик возобновления сервера---------------------------------
    ActionListener reconnectListener = new ActionListener(){
    public void actionPerformed(ActionEvent e) {
     NetworkAddress address = (NetworkAddress)e.getSource();
     address.SetEnable();
     try{
      if(slaveServerList.get(address).isInNet() == 1){
       activateClientListener(e, listReconnectAfterFailListener);
     }else{
       activateClientListener(e, listReconnectListener);
      }
     }catch(RemoteException re){}//Тут обработка не нужна - пинг сам обнаружит и подправит
     //Удалить сервис принудительного подключения из списка сервисов
     ReconnectService temp = listReconnectSS.get(address);
     temp.stopPing();
     listReconnectSS.remove(address);
    }
   };
   //-------------------------------------------------------------------------

   rs.addServerConnectListener(reconnectListener);
  }

  /**
   * Активировать пользовательские обработчики событий
   * @param event информация об источнике события
   * @param list список обработчиков
   */
  private void activateClientListener(ActionEvent event, LinkedList<ActionListener> list){
    Iterator<ActionListener> iter = list.iterator();
    while(iter.hasNext()){
     iter.next().actionPerformed(event);
    }
  }

  /**
   * Преобразровать URL адрес в RMI адрес
   * @param inetAddress - адрес хоста
   * @param port - адрес порта
   * @param stubName - имя заглушки
   */
  private String transformToRMIURL(String inetAddress, int port, String stubName){
     inetAddress.trim();
     if(inetAddress.contains("/" + stubName)) inetAddress = inetAddress.substring(0, inetAddress.length() - stubName.length() - "/".length());
     inetAddress+= ":" + port + "/"+ stubName;

     if( (!inetAddress.contains("http") && (!inetAddress.contains("rmi")) )) inetAddress = "rmi:" + "/" + "/" + inetAddress;
     else inetAddress =  inetAddress.replace("http", "rmi");
     return inetAddress;
  }
   //------------------------------------------------------------------

   //--------------Интерфейсные методы---------------------------------

  /**
   * Создание объекта.
   * @throws java.io.IOException доступ к файлу настроек арбитра невозможен
  */
  public ArbitrationController() throws IOException{
    try{
     loadAddresses();
    }catch(IOException ioe){
     //Сгенерировать уникальный идентификатор арбитра
     s2sInnerLogic.GUIDServer = generateServerID();
     saveAddresses();
   }
  }

    /**
   * Добавить ведомый сервер
   * @param address - полная информация о подключаемом сервере
   * @return интерфейс заглушки - подключенгие нового сервера завершилось успешно, null - в противном случае
   * @throws java.rmi.RemoteException - разрыв соединения при попытке получения удалённого объекта
   * @throws java.rmi.NotBoundException - удалённый объект не корректен или заданный сервер не выставил заглушку
   * @throws java.net.MalformedURLException - ошибка в URL хоста
   * @throws s2sNetwork.ServerTypeException - сервер является ведомым и не может подключать новые сервера
  */
  public ISlaveServer addNewSlaveServer(NetworkAddress address) throws RemoteException, NotBoundException, MalformedURLException, ServerTypeException{
   return addNewSlaveServer(address.getHost(), address.getPort());
  }

  /**
   * Добавить ведомый сервер
   * @param host - хост ведомого сервера
   * @param port - порт ведомого сервера
   * @return интерфейс заглушки - подключенгие нового сервера завершилось успешно, null - в противном случае
   * @throws java.rmi.NotBoundException - удалённый объект не корректен или заданный сервер не выставил заглушку
   * @throws java.net.MalformedURLException - ошибка в URL хоста
   * @throws s2sNetwork.ServerTypeException - сервер является ведомым и не может подключать новые сервера
   */
  public ISlaveServer addNewSlaveServer(String host, int port) throws NotBoundException, MalformedURLException, ServerTypeException, MalformedURLException{

   //Проверка а не является ли сервер подчинённым
   if(s2sInnerLogic.mode == SLAVE_MODE) {
    String message = "Сервер является подчинённым и не может подключать другие сервера";
    throw new ServerTypeException(s2sInnerLogic.mode, message);
   }
   //Проверка работает ли сервер в автономном режиме, необходима чтобы зафиксировать факт,
   //перехода работы сервера в режим арбитра
   boolean isAutoMode = false;
   if(s2sInnerLogic.mode == AUTONOMIC_MODE){
    isAutoMode = true;
   }

   String inetAddress = transformToRMIURL(host, port, STUBSLAVENAME); //Преобразовать входной адрес в URI по формату RMI
   //---------------------Установить диспетчер защиты------------------------------------------------------------
   NetworkAddress address = new NetworkAddress(host,port);
   //Устанавливаем диспетчер защиты RMI
   System.setProperty("java.security.policy", "server.policy");
   RMISecurityManager manager = new RMISecurityManager();
   System.setSecurityManager(manager);

   //--------------------------------Получение класса заглушки, сохранение его в таблицу--------------------------
   ISlaveServer slaveServer = null; //Ссылка на заглушку ведомого сервера
   int remoteMode = IModeWork.AUTONOMIC_MODE; //Предполагаемый режим работы удалённого сервера
   long result = -1;  // может ли удалённый сервер стать ведомым?
   try{
   slaveServer = (ISlaveServer)Naming.lookup(inetAddress); //Получение заглушки
   result = slaveServer.connect( NetworkLogic.getIP() , NetworkLogic.getPort()); //Попытка соединения
   remoteMode = slaveServer.getMode(); //Получаем режим работы удалённого сервера
   }catch(RemoteException re){
    //Здесь проиходит инициализация сервиса отложенного подкючения для этого сервера
    boolean temp = false; 
    Iterator iter = listReconnectSS.keySet().iterator();
    while(iter.hasNext()){
     if(iter.next().equals(address)) {temp = true; break;}
    }
    if(temp) return null;
    ReconnectService rs = new ReconnectService(this, address);
    rs.addServerConnectListener(new ActionListener(){
      public void actionPerformed(ActionEvent e) {
       NetworkAddress address = (NetworkAddress)e.getSource();
       listReconnectSS.get(address).stopPing();
       listReconnectSS.remove(address);
       System.out.println("Обработчик принудительного подключения сервера был удалён");
      }
    });
    listReconnectSS.put(address, rs);
    rs.startPing();
    address.SetDisable();
    if(!slaveServerList.containsKey(address)) slaveServerList.put(address, null);
    Iterator<NetworkAddress> iter2 =  slaveServerList.keySet().iterator();
    while(iter2.hasNext()){
     NetworkAddress adrs = iter2.next();
     System.out.println("" + adrs.getHost() + " " + adrs.getPort() );
    }
    if(s2sInnerLogic.mode != ARBITRATOR_MODE){
     //Сообщение о том, что текущий сервер стал арбитром
     s2sInnerLogic.mode = ARBITRATOR_MODE;
     this.activateClientListener(null, listActionModeArbitrator);
    }
    return null; 
   }
   if(result != -1){ //Если сервер подтвердил что он может стать подчинённым, то добавляем его в список подчинённых адресов
    address.setGUID(result); 
    slaveServerList.remove(address);
    slaveServerList.put(address, slaveServer);
    //Сообщение о том, что текущий сервер стал арбитром
    if(s2sInnerLogic.mode != ARBITRATOR_MODE ) activateClientListener(null, listActionModeArbitrator);
    s2sInnerLogic.mode = ISlaveServer.ARBITRATOR_MODE;

   }else{ //Если тип сервера не позволяет серверу стать подчинённым то завершаем обработку
    String message = "Сервер: "+address.getHost()+" , порт: "+ address.getPort() +
                     " является подчинённым и не может подключать другие сервера";
    throw new ServerTypeException(remoteMode, message);
   }
   //Установка сервиса пингования ведомых серверов-----------------------
   PingService service = new PingService(slaveServer,address);
   listPingSS.add(service);
   service.startPing();
   initPingListener(service);
   //--------------------------------------------------------------------
   //Проверка сменил ли сервер режим работы на арбитра или он уже был арбитром до
   //данной операции
   if(isAutoMode == true && s2sInnerLogic.mode == ARBITRATOR_MODE){
    activateClientListener(null, listActionModeArbitrator);
   }
   //Активация обработчиков события "Добавлен новый сервер"
   ActionEvent eventAdd = new ActionEvent(address,0,null);
   activateClientListener(eventAdd, listActionAddSlaveArbitrator);
   return slaveServer;
  }

  /**
   * Добавить ведомый сервер, с вызовом сервисов анализа и восстановления соединения
   * @param address адрес ведомого сервера
   * @return интерфейс заглушки - подключенгие нового сервера завершилось успешно, null - в противном случае
   */
  private ISlaveServer addNewSlaveServerWithServices(NetworkAddress address){

    try {
     return addNewSlaveServer(address.getHost(), address.getPort());
        }catch (NotBoundException ex) {
          //TODO
          //Говорит о том, что соединение от арбитра до ведомого сервера прошло, но
          //обратный канал не был открыт. Возможные проблемы:
          //1) Проблемы при создании заглушки арбитра, необходимо реализовать сервис
          //   диагностики и восстановления
          //2) Неверны параметры текущей машины, необходимо проверить параметры
          //   текущего сервера
         JOptionPane.showMessageDialog(null, "Проблема открытия канала связи: Арбитр-ведомый сервер");
         return null;
        } catch (MalformedURLException ex) {
          //TODO
          //Говорит о том, что одна из URL удалённого сервера синтаксически не верна:
          //Возможные причины: файл повреждён или был изменён злоумышленниками
          //Решение: необходимо предусмотреть сервис оповещения администратора для
          //редактирования URL  и сервис восстановления заданного URL по журналу
         JOptionPane.showMessageDialog(null, "Ошибка URL сервера");
         return null;
        } catch (ServerTypeException ex) {
          //TODO
          //Удалённый сервер больше не может являться подчинённым, так как его политика
          //была изменена
          //Решение: необходимо расширить количество состоянии сервера - и ввести состояние
          //"недопустимая политика"
         JOptionPane.showMessageDialog(null, "Политика сервера была явно изменена на недопустимую");
         return null;
        }
  }

  /**
   * @author Aleksey,Volk
   * Сохранить таблицу адресов в файле настроек
   * @throws java.io.IOException ошибка при работе с файлом
   * @version 2.0 Volk: В формат файла добавлены параметры текущего сервера
   *                    введено понятие формата файла (константа FORMATV)
   */
  public void saveAddresses() throws IOException{
        //создать поток
        ObjectOutputStream stream = new ObjectOutputStream
                (new FileOutputStream(SETTINGS_PATH));
        //сохранить записи
        try{
            //Сохраняем формат файла
            stream.writeUTF(FORMATV);
            //Сохраняем значение генератора случайных чисел
            stream.writeLong(generatorServerID);
            //пройти по всем элементам хэш таблицы параметров
            Set <NetworkAddress> keys = slaveServerList.keySet();
            for(NetworkAddress tmpKey:keys){
                //записать в файл
                stream.writeUTF(tmpKey.getHost());
                stream.writeInt(tmpKey.getPort());
                stream.writeLong(tmpKey.getGUID());
            }
            //запись в файл
            stream.flush();
        }
        //закрыть файл
        finally{ stream.close(); }
  }

  /**
   * @author Aleksey, Volk
   * Загрузить таблицу адресов из файла настроек
   * @throws java.io.IOException ошибка при работе с файлом (файл не найден, блокирован или имеет несовместимый или повреждённый формат)
   * @version 2.0 Добавлена отложенная загрузка с проверкой корректности формата
   *              , в случае если файл был поврежён или формат файла не соответсвует, то
   *              будет сгенерировано исключение, а текущие данные будут восстановлены.
   *              Введена проверка на совпадения версии форматов файла настроек
   */
  public void loadAddresses() throws IOException{
        //создать поток

       ObjectInputStream stream = new ObjectInputStream
            (new FileInputStream(SETTINGS_PATH));
       try{
            //Проверка на совпадение форматов
            String version = stream.readUTF();
            if(!version.equals(FORMATV)) throw new IOException();
            //Загружаем значение генератора случайных чисел
            generatorServerID = stream.readLong();
            NetworkAddress tmpAddr;
            LinkedList<NetworkAddress> lst = new LinkedList<NetworkAddress>();
            //перебор всех значений
            while(stream.available() != 0){
             //прочитать значение адреса и порта
             tmpAddr=new NetworkAddress(stream.readUTF(), stream.readInt());
             long guid = stream.readLong();
             lst.add(tmpAddr);
            }

         //очистить текущие значения
         slaveServerList.clear();

         Iterator iter = lst.iterator();
         while(iter.hasNext()){
           NetworkAddress temp = (NetworkAddress)iter.next();
           slaveServerList.put(temp, null);
           }
        }
        //закрыть файл
        finally{ stream.close(); }
  }


     /**
     * Добавить обработчик события "Потеря соединения"
     * @param listener - обработчик
     */
    public void addFailServerListener(ActionListener listener){
        listFailListener.add(listener);
    }

    /**
     * Добавить обработчик события "Возобновление соединения"
     * @param listener - обработчик
     */
    public void addReconnectServerListener(ActionListener listener){
       listReconnectListener.add(listener);
    }

    /**
     * Добавить обработчик события "Удаление ведомого сервера"
     * @param listener - обработчик
    */
    public void addRemoveServerListener(ActionListener listener){
       listRemoveServerListener.add(listener);
    }
    /**
     * Удалить все обработчики события "Потеря соединения"
     */
    public void removeAllFailServerListener(){listFailListener.clear();}

    /**
    * Удалить все обработчики события "Восстановление соединения"
    */
    public void removeAllReconnectServerListener(){listReconnectListener.clear();}

    /**
    * Удалить все обработчики событий
    */
    public void removeAllListener(){removeAllFailServerListener(); removeAllReconnectServerListener();}


    /**
     * Обновить список серверов
     * @param collection коллекция обновлённых серверов
     */
    public void mergeServerList(Collection<NetworkAddress> collection){
      //Преобразовываем исходные входные структуры данных в единую форму удобную для обработки
      LinkedList<LinkedList<NetworkAddress>> curList = toLinkedList2(slaveServerList.keySet());
      LinkedList<LinkedList<NetworkAddress>> newList = toLinkedList2(collection);
      //Организуем сливание этих двух множеств
      notCrossSetLinkedList(curList, newList);

      //Организуем удаление серверов из первого подмножества
      Iterator<LinkedList<NetworkAddress>> iterCur = curList.iterator();
      while(iterCur.hasNext()){
        LinkedList<NetworkAddress> lst1 = iterCur.next();
        Iterator<NetworkAddress> iter1 = lst1.iterator();
        while(iter1.hasNext()){
          NetworkAddress address = iter1.next();
          //-------------ЗАГЛУШКА----------------------------------
          //Соединение может быть потеряно при удалении из чего следует
          //что необходим сервис отложенного удаления
          try{
           remove(address);
          }catch(RemoteException re){
           System.out.println("Сервер не был удалён из системы из-за разрыва соединения");
          }
          //--------------------------------------------------------
        }
      }

      //Организуем добавление новых серверов
      Iterator<LinkedList<NetworkAddress>> iterCol = newList.iterator();
      while(iterCol.hasNext()){
        LinkedList<NetworkAddress> lst2 = iterCol.next();
        Iterator<NetworkAddress> iter2 = lst2.iterator();
        while(iter2.hasNext()){
         NetworkAddress address = iter2.next();

         //-----Добавление новых серверов в таблицу серверов-------
         ISlaveServer server = addNewSlaveServerWithServices(address);
         if(server == null) continue;  //Если добавление сервера провалилось, то перейти к след кандидату
         //--------------------------------------------------------
        }
      }
    }

    /**
     * Получить список серверов
     * @return список серверов
     */
    public NetworkAddress[] getServersList(){
     if(slaveServerList == null) return null;
     Set<NetworkAddress> set = slaveServerList.keySet();
     if(set.isEmpty()) return null;
     Iterator<NetworkAddress> iter = set.iterator();
     NetworkAddress[] result = new NetworkAddress[set.size()];
     int i = 0;
     while(iter.hasNext()){
     result[i++] = iter.next();
     }
     return result;
    }

    /**
    * Проверяет запрос об сетевой активности сервера
    * @throws java.rmi.RemoteException - сервер не доступен
    */
	public void ping() throws RemoteException{}

   /**
    * Удалить сервер из сети
    * @param  address адрес удаляемого сервера
    * @return true сервер успешно удалён, false сервер с таким адресом не зарегистрирован в системе
    * @throws java.rmi.RemoteException разрыв соединения
    */
   public boolean remove(NetworkAddress address) throws RemoteException{
     if(!slaveServerList.containsKey(address)) return false;
     try{
     slaveServerList.get(address).remove(); //Сообщаем ведомому серверу о том, что он удалён из сети
     }catch(IOException ioe){throw new RemoteException();}
     slaveServerList.remove(address); //Уgдаляем из списка серверов
     //Останавливаем сервисы связанные с пингованием текущего сервера
     Iterator<PingService> iter = listPingSS.iterator();
     while(iter.hasNext()){
      PingService ps = iter.next(); 
      ps.stopPing();
      iter.remove();
      break; 
     }
     
     //Если ведомых серверов больше не осталось, то объявляем о том, что сервер работает в
     //автономном режиме
     if(slaveServerList.isEmpty()) {s2sInnerLogic.mode = AUTONOMIC_MODE;
       //Оповещаем все обработчики об этом событии
       NetworkLogic.ActivateEventAutomaticMode();
     }
     //Оповещение обработчиков события: Удаление ведомого сервера
     ActionEvent revent = new ActionEvent(address, 0,null);
     activateClientListener(revent, listRemoveServerListener);
     return true;
   }

    /**
     * Обновить параметры сервера
     * @param oldAddress старые параметры сервера
     * @param newAddress новые параметры сервера
     * @return true - обновление завершилось успешно
     * @throws java.rmi.RemoteException false - обновление завершилось провалом
     */
   @Override
   public boolean slaveUpdateConfig(NetworkAddress oldAddress, NetworkAddress newAddress) throws RemoteException{
     //Заменяем старый адрес на новый------------------------
     ISlaveServer server = slaveServerList.get(oldAddress);
     if(server == null) return false;
     slaveServerList.remove(oldAddress);
     slaveServerList.put(newAddress, server);
     //-------------------------------------------------------
     //Помещаем в список изменённых---------------------------
     slaveUpdateConfigList.put(oldAddress, newAddress);
     //-------------------------------------------------------
     try{
     saveAddresses();
     //Арбитр не может сохранить информацию о сервере
     }catch(IOException ioe){return false;}
     //Восстанавлимаем старые параметры-----------------------
     server = slaveServerList.get(newAddress);
     slaveServerList.remove(newAddress);
     slaveServerList.put(oldAddress, server);

     //-------------------------------------------------------
     
     return true;
   }

  /**
   * запросить уникальный идентификатор сервера у арбитра
   * @return уникальный идентификатор сервера
   */
  public long generateServerID() throws RemoteException{
   return generatorServerID++;
  }

    /**
     * Запросить заглушку ведомого сервера по адресу
     * @param address адрес сервера
     * @return заглушка ведомого
     * @author Aleksey ID=2001
     */
    public ISlaveServer getSlaveStub(NetworkAddress address) throws RemoteException{
        if(address==null) throw new NullPointerException();
        return slaveServerList.get(address);
    }

    /**
     * Добавить обработчик события: "Сервер стал арбитром"
     * @param listener обработчик события
     */
    public void addModeArbitratorListener(ActionListener listener){
     listActionModeArbitrator.push(listener);
    }

     /**
     * Добавить обработчик события: "Добавлен новый ведомый сервер"
     * @param listener обработчик события
     */
    public void addAddSlaveArbitratorListener(ActionListener listener){
     listActionAddSlaveArbitrator.push(listener);
    }



    /**
     * Добавить обработчик события: "Ведомый сервер восстановлен после падения"
     * @param listener обработчик события
     */
    public void addReconnectAfterFailListener(ActionListener listener){
     listReconnectAfterFailListener.push(listener);
    }
   //-------------------------------------------------------------------
   //реализация методов ITaskManager
   /** обработчики событий ведомых серверов */
   private Vector<MpiListener> listeners;

   /**
    * Добавить обработчик событий ведомого сервера
    * @param listener обработчик события
    * @author Aleksey ID=2001
    */
    public void addMpiListener(MpiListener listener){
        if(listener==null) throw new NullPointerException();
        //проверить на наличие такого обработчика, если его нет - добавить
        if(listeners.contains(listener)) return;
        listeners.add(listener);
    }

    /**
     * Обработчик события окончания задания
     * перенаправить событие на все обработчики
     * @param e событие
     * @author Aleksey ID=2001
     */
    public void endingTask(MpiEvent e) throws RemoteException {
        for(MpiListener l:listeners)
            l.endingTask(e);
    }

    /**
     * Обработчик события ошибки запуска задания
     * перенаправить событие на все обработчики
     * @param e событие
     * @author Aleksey ID=2001
     */
    public void errorTask(MpiEvent e) throws RemoteException{
        for(MpiListener l:listeners)
            l.errorTask(e);
    }


}
